package com.net.j8583;

import com.net.j8583.field.Field;
import com.net.j8583.field.FieldComp;
import com.net.utils.ByteUtil;
import com.net.utils.ExceptionUtils;
import com.net.utils.SocketProcessor;

import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Date: 13-11-15
 * Time: 13:42
 */
public abstract class Protocol8583<REQ, RESP> implements SocketProcessor {


    protected Map<Integer, Field> requestData = new LinkedHashMap<Integer, Field>();

    protected Map<Integer, Field> responseHeader = new LinkedHashMap<Integer, Field>();

    protected Map<Integer, Field> responseData = new LinkedHashMap<Integer, Field>();

    protected LinkedHashMap<Integer, Field> defineFieldBody;

    protected LinkedHashMap<Integer, Field> defineFieldHeader;


    protected List<Byte> dataByte = new LinkedList<Byte>();

    public static final int FIELD_LEN = 64;

    protected String zmk;

    protected String zak;

    protected boolean needMac = true;


    protected REQ req;

    protected Protocol8583(REQ req, Define8583 define8583) {
        this.req = req;
        this.defineFieldBody = define8583.getDefineFieldBody();
        this.defineFieldHeader = define8583.getDefineFieldHeader();
        initRequestData();
        initResponseData();
    }

    public void addFiled(Integer index, String str) {
        Field field = requestData.get(index);
        if (field == null) {
            throw new RuntimeException("Field" + index + " undefined");
        }
        if (field.isAutoFill()) {
            throw new RuntimeException("this Field is AutoFill,not allow to add");
        }
        field.setReqSrcValue(str);
        requestData.put(index, field);
    }

    public void addSubField(Integer parentIndex, Integer subIndex, String str) {
        Field field = defineFieldBody.get(parentIndex);
        if (field == null) {
            throw new RuntimeException("Field" + parentIndex + " undefined");
        }
        if (!(field instanceof FieldComp)) {
            throw new RuntimeException("Field is not a instants of FieldComp,class name : " + field.getClass().getName());
        }
        FieldComp fieldComp = (FieldComp) field;
        fieldComp.addSubField(subIndex, str);
        requestData.put(parentIndex, fieldComp);
    }

    public byte[] makeRequestContent() {
        putRequestParam();
        for (Map.Entry<Integer, Field> entry : requestData.entrySet()) {
            Field field = entry.getValue();
            if (!field.isAutoFill() && field.getReqSrcBytes().length == 0) {
                continue;
            }
            byte[] fieldByte = field.toByte();
            System.out.println(field.requestInfo());
            dataByte.addAll(ByteUtil.byteArray2List(fieldByte));
        }
        return ByteUtil.list2ByteArray(dataByte);
    }

    public RESP makeResponseObject(byte[] response) {
        ByteBuffer byteBuffer = ByteBuffer.wrap(response);
        for (Map.Entry<Integer, Field> entry : responseHeader.entrySet()) {
            Field field = entry.getValue();
            field.decode(byteBuffer, this);
        }
        Integer keyIndex = null;
        try {
            for (Map.Entry<Integer, Field> entry : responseData.entrySet()) {
                keyIndex = entry.getKey();
                Field field = entry.getValue();
                field.setFieldId(keyIndex);
                field.decode(byteBuffer, this);
                String info = field.responseInfo();
                System.out.println(info);
            }
            return toResult();
        } catch (Exception e) {
            ExceptionUtils.printException(e);
            throw new RuntimeException("read protocol 8583 fail");
        }

    }

    public abstract void putRequestParam();

    public abstract RESP toResult();


    private void initRequestData() {
        Map headerMap = (Map) defineFieldHeader.clone();
        Map bodyMap = (Map) defineFieldBody.clone();
        requestData.putAll(headerMap);
        requestData.putAll(bodyMap);
        for (Map.Entry<Integer, Field> entry : requestData.entrySet()) {
            Field field = entry.getValue();
            field.setFieldId(entry.getKey());
            field.setProtocol(this);
        }
    }

    private void initResponseData() {
        responseHeader = (LinkedHashMap) defineFieldHeader.clone();
        for (Map.Entry<Integer, Field> entry : responseHeader.entrySet()) {
            Field field = entry.getValue();
            field.setFieldId(entry.getKey());
            field.setProtocol(this);
        }
        responseData = (Map<Integer, Field>) defineFieldBody.clone();
    }

    public void addResponseData(Integer index, Field field) {
        field.setFieldId(index);
        responseData.put(index, field);
    }

    public Map<Integer, Field> getRequestData() {
        return requestData;
    }

    public String getZmk() {
        return zmk;
    }

    public void setZmk(String zmk) {
        this.zmk = zmk;
    }

    public String getZak() {
        return zak;
    }

    public void setZak(String zak) {
        this.zak = zak;
    }

    public boolean isNeedMac() {
        return needMac;
    }

    public void setNeedMac(boolean needMac) {
        this.needMac = needMac;
    }

    public Field getFieldInfo(Integer index) {
        Field field = defineFieldBody.get(index);
        field.setFieldId(index);
        return field;
    }

    public Integer getMacFieldIndex() {
        return 64;
    }

    @Override
    public byte[] readTcpResponse(DataInputStream tcpResponse) {
        try {
            int len = tcpResponse.readUnsignedShort();
            byte[] bb = new byte[len];
            tcpResponse.readFully(bb);
            return bb;
        } catch (IOException e) {
            ExceptionUtils.printException(e, 5);
            throw new RuntimeException(e.toString());
        }
    }

    @Override
    public byte[] writeTcpHeader(byte[] requestData) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(requestData.length + 2);
        byte[] lenBytes = new byte[2];
        ByteUtil.writeShort(lenBytes, 0, requestData.length);
        byteBuffer.put(lenBytes);
        byteBuffer.put(requestData);
        return byteBuffer.array();
    }

    public void removeRedundancyHeaderField() {
        for (Integer integer : responseHeader.keySet()) {
            responseData.remove(integer);
        }
    }

    public void removeResponseField(Integer index) {
        responseData.remove(index);
    }
}
